use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;

use super::LruXcache;
use super::DataField;

pub struct LruHashXcache<T:LruHashXcacheItem> {
    inner:LruXcache<T>,
    id_hash:HashMap<T::Id,usize>,
}

impl<T:LruHashXcacheItem> LruHashXcache<T> {
    pub fn with_capacity(cap:usize) -> Self {
        Self {
            inner:LruXcache::with_capacity(cap),
            id_hash:HashMap::new(),
        }
    }

    pub fn insert(&mut self,t:T) {
        let id = t.id();
        let node_no = self.inner.insert_with_drop(t,|t|{
            self.id_hash.remove(&t.id());
        });
        self.id_hash.insert(id,node_no);
    }

    pub fn insert_node(&mut self,node:DataField<T>) {
        self.inner.insert_node(node)
    }

    pub fn iter(&self) -> impl std::iter::Iterator + '_ {
        self.inner.iter()
    }
}


impl<T:LruHashXcacheItem + Clone> LruHashXcache<T> {
    pub fn find(&self,id:T::Id) -> Option<DataField<T>> {
        let no = self.id_hash.get(&id)?;
        let data_field = self.inner.get(*no);
        Some(data_field.clone())
    }
}

pub trait LruHashXcacheItem {
    type Id:Hash + Eq;
    fn id(&self) -> Self::Id;
}

impl<P> LruHashXcacheItem for P 
        where P:Deref , P::Target:LruHashXcacheItem {
    type Id = <<P as Deref>::Target as LruHashXcacheItem>::Id;
    fn id(&self) -> Self::Id {
        (self as &P::Target).id()
    }
}


#[derive(Clone,PartialEq, Eq,Debug)]
struct StringBlock {
    id:usize,
    inner:String,
}

impl StringBlock {
    fn new(id:usize,s:&str) -> Self { Self { id:id, inner:s.to_string() } }
}

impl LruHashXcacheItem for StringBlock {
    type Id = usize;
    fn id(&self) -> Self::Id {
        self.id
    }
}

const TEST_CACHE_CAPACITY:usize = 4;

pub fn lru_hash_xcache_test() {
    let test_input:Vec<StringBlock> = vec![
        "aacacwnqn",        //0
        "bncnacnis",        //1
        "cacacacbqiub",     //2
        "uhanciacaiu",      //3
        "gdncanncancacc",   //4
        "jnjbvyvy"          //5
    ]
        .iter()
        .enumerate()
        .map(|(i,s)|StringBlock::new(i,s))
        .collect();
    let mut cache = LruHashXcache::with_capacity(TEST_CACHE_CAPACITY);

    for test in test_input.iter() {
        let id = test.id();
        let result = cache.find(id);
        if result.is_none() {
            cache.insert(test.clone());
            let result = cache.find(id);
            assert_eq!(&result.unwrap() as &StringBlock,test);
        } else {
            assert_eq!(&result.unwrap() as &StringBlock,test);
        }
    }
    let result = cache.find(4);
    cache.insert_node(result.unwrap());
    println!("{:?}",cache.inner);
    let result = cache.find(5);
    cache.insert_node(result.unwrap());
    println!("{:?}",cache.inner);
}